package uu.game.majiang.core;

import uu.game.majiang.core.rule.paixing.HuPaixing;
import uu.game.majiang.core.rule.paixing.PaixingAnalyser;
import uu.game.majiang.core.rule.paixing.gb.GbPaixingAnalyser;

/**
 * 游戏的核心类，表示一个玩家在一盘中手里的牌
 * 
 * <pre>
 * 包括手里的暗牌，吃碰杠后的明牌、补的花牌、刚摸到的牌
 * 
 * 实例的生命周期应该是一盘
 * 实例与一盘中其他牌的实例需要有共存关系，此排他性(四个人的牌共存于一整副麻将)需要调用者自己控制，应该通过一盘的全部牌来构造此实例
 * </pre>
 * 
 * @author uu
 * @see Pais
 * @see Pan
 * @version 2010-4-21 下午02:15:33
 */
/*
 * 整个库依赖于此类中定义的一些数值及其数据结构
 * 这些数据存在很多内部约定，它们的值已经硬编码在代码中
 */
public final class Pai {

    /**
     * 吃的牌放在三张牌左边
     */
    public static final int CHI_LEFT      = -1;

    /**
     * 吃的牌放在三张牌中间
     */
    public static final int CHI_CENTER    = -2;

    /**
     * 吃的牌放在三张牌右边
     */
    public static final int CHI_RIGHT     = -3;

    /**
     * 碰下家
     */
    public static final int PENG_XIAJIA   = -4;

    /**
     * 碰对家
     */
    public static final int PENG_DUIJIA   = -5;

    /**
     * 碰上家
     */
    public static final int PENG_SHANGJIA = -6;

    /**
     * 明杠下家
     */
    public static final int GANG_XIAJIA   = -7;

    /**
     * 明杠对家
     */
    public static final int GANG_DUIJIA   = -8;

    /**
     * 明杠上家
     */
    public static final int GANG_SHANGJIA = -9;

    /**
     * 明杠碰过的牌
     */
    public static final int GANG_PENG     = -10;

    /**
     * 暗杠
     */
    public static final int GANG_AN       = -11;

    /**
     * 和下家
     */
    public static final int HU_XIAJIA     = -12;

    /**
     * 和对家
     */
    public static final int HU_DUIJIA     = -13;

    /**
     * 和上家
     */
    public static final int HU_SHANGJIA   = -14;

    /**
     * 和自己摸到的牌<br/>
     * 不一定算自摸
     */
    public static final int HU_ZIJI       = -15;

    /*
     * 共144张牌
     * 整个程序依赖于每张牌的编号
     * 通过调用Pais.getAllPai()可以得到此数组的拷贝
     * 通过调用Pais.xipai()可以得到此数组洗牌后的的随机顺序拷贝
     */
    private static final int[] ALL_PAI = {

            11, 11, 11, 11, // 一万
            12, 12, 12, 12, // 二万
            13, 13, 13, 13, // 三万
            14, 14, 14, 14, // 四万
            15, 15, 15, 15, // 五万
            16, 16, 16, 16, // 六万
            17, 17, 17, 17, // 七万
            18, 18, 18, 18, // 八万
            19, 19, 19, 19, // 九万

            21, 21, 21, 21, // 一条
            22, 22, 22, 22, // 二条
            23, 23, 23, 23, // 三条
            24, 24, 24, 24, // 四条
            25, 25, 25, 25, // 五条
            26, 26, 26, 26, // 六条
            27, 27, 27, 27, // 七条
            28, 28, 28, 28, // 八条
            29, 29, 29, 29, // 九条

            31, 31, 31, 31, // 一饼
            32, 32, 32, 32, // 二饼
            33, 33, 33, 33, // 三饼
            34, 34, 34, 34, // 四饼
            35, 35, 35, 35, // 五饼
            36, 36, 36, 36, // 六饼
            37, 37, 37, 37, // 七饼
            38, 38, 38, 38, // 八饼
            39, 39, 39, 39, // 九饼

            41, 41, 41, 41, // 东
            42, 42, 42, 42, // 南
            43, 43, 43, 43, // 西
            44, 44, 44, 44, // 北

            51, 51, 51, 51, // 中
            52, 52, 52, 52, // 发
            53, 53, 53, 53, // 白

            61, // 春
            62, // 夏
            63, // 秋
            64, // 冬
            65, // 梅
            66, // 兰
            67, // 竹
            68  // 菊
    };
    
    /*
     * 存储牌的数据(不包括摸到的牌)
     * 
     * 此数组是所有操作的基础
     * 
     * 外部需要了解此数组结构
     * 如果隐藏此结构，需要写很多方法，并且很难考虑到所有可能的使用方式
     * 所以在Hupai类中对外公布结构，参见Hupai.getData()和Hupai.getFullData()
     * 注意Hupai向外提供此数组时会加上要和的牌，参见canHu()和hu()方法，而通常此数组只包含13张牌
     * 
     * 存储的结构分为12部分，分别为：
     * 
     * 1 位置0
     *      和牌类型
     *          未和牌：0
     *          和下家：HU_XIAJIA
     *          和对家：HU_DUIJIA
     *          和上家：HU_SHANGJIA
     *          自摸  ：HU_ZIJI
     * 
     * 2 位置1~9
     *      存放吃碰杠的牌
     *      其中位置1存放吃碰杠的次数，从第2个元素开始按照发生时间顺序存放每次吃碰杠的表示
     *      每次吃碰杠表示为2个元素：类型和牌号
     *          吃到的牌表示为(放的是吃到的牌，不是最小的牌)
     *              吃到的牌吃后放在左边：CHI_LEFT   吃的牌号
     *              吃到的牌吃后放在中间：CHI_CENTER 吃的牌号
     *              吃到的牌吃后放在右边：CHI_RIGHT  吃的牌号
     *          碰到的牌 表示为
     *              碰下家：PENG_XIAJIA   碰的牌号
     *              碰对家：PENG_DUIJIA   碰的牌号
     *              碰上家：PENG_SHANGJIA 碰的牌号
     *          杠到的牌 表示为
     *              明杠下家：GANG_XIAJIA   明杠的牌号
     *              明杠对家：GANG_DUIJIA   明杠的牌号
     *              明杠上家：GANG_SHANGJIA 明杠的牌号
     *              暗杠    ：GANG_AN       暗杠的牌号
     *              (碰后再明杠也表示为前三种明杠)
     * 
     * 3 位置10~19
     *      存放手里的万牌(吃碰杠后剩下的牌)
     *      位置10存放万牌的总数 位置11~19分别存放1到9万的数量
     * 
     * 4 位置20~29
     *      存放手里的条牌(吃碰杠后剩下的牌)
     *      位置20存放条牌的总数 位置21~29分别存放1到9条的数量
     * 
     * 5 位置30~39
     *      存放手里的饼牌(吃碰杠后剩下的牌)
     *      位置30存放饼牌的总数 位置31~39分别存放1到9饼的数量
     * 
     * 6 位置40~44
     *      存放手里的风牌(吃碰杠后剩下的牌)
     *      位置40存放风牌的总数 位置41~44分别存放东风，南风，西风，北风的数量
     * 
     * 7 位置45
     *      判断符合基本和牌形式时，将牌的牌号，特殊牌型此位置数据无意义
     * 
     * 8 位置46~49
     *      判断符合基本和牌形式(33332形式)时，顺子的牌号(第1张)，正序排列，特殊牌型此数据只保存吃的顺
     * 
     * 9 位置50~53
     *      存放手里的箭牌(吃碰杠后剩下的牌)
     *      位置50存放箭牌的总数 位置51~53分别存放红中，发财，白板的数量
     * 
     * 10 位置54~57
     *      判断符合基本和牌形式(33332形式)时，刻牌的牌号，正序排列，特殊牌型此数据只保存碰杠的刻
     * 
     * 11 位置58~59
     *      空闲，留用
     * 
     * 12 位置60~68
     *      存放手里的花牌(包括补过的和未补的)
     *      位置60存放补过的花牌的数量(只是补过的)
     *      位置61~68分别对应春夏秋冬梅兰竹菊8张花牌，未摸到表示为0，摸到了表示为1，摸到并补花了表示为-1
     */
    private final int[] data = {
            0,                            // 位置 0     : 和牌类型
            0, 0, 0, 0, 0, 0, 0, 0, 0,    // 位置 1~9   : 吃碰杠
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 10~19 : 万
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 20~29 : 条
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 30~39 : 饼
            0, 0, 0, 0, 0,                // 位置 40~44 : 风
            0,                            // 位置 45    : 将牌牌号
            0, 0, 0, 0,                   // 位置 46~49 : 顺子
            0, 0, 0, 0,                   // 位置 50~53 : 箭
            0, 0, 0, 0,                   // 位置 54~57 : 刻
            0, 0,                         // 位置 58~59 : 空闲，留用
            0, 0, 0, 0, 0, 0, 0, 0, 0     // 位置 60~68 : 花
     };

    /*
     * 大部分情况下存储最后一次摸到的1张牌。
     * 当吃碰杠时，存储手里的最后1张牌(此时需要扔1张牌，与摸到牌后情况类似)
     */
    private int modao;

    /*
     * 封装和牌信息。
     * Hupai中有好几个缓存数组，考虑到效率，在每次判断是否和牌时都重用这一个实例
     */
    private Hupai hupai = new Hupai();
    
    /*
     * 圈风牌号
     */
    private int quanfeng;

    /*
     * 门风牌号
     */
    private int menfeng;

    /*
     * 牌型解析器，默认使用国标
     */
    private PaixingAnalyser paixingAnalyser = new GbPaixingAnalyser();

    /**
     * 通过牌的数组和一张摸到的牌构造实例
     * <hr/>
     * <style>table{border-collapse:collapse;}td{border:1px solid green;}.fan{background-color:#BAEB67;}</style>
     * 每张牌的牌号如下：
     *  <table>                                                                                                  
     *  <tr class="fan"><td>一万</td><td>二万</td><td>三万</td><td>四万</td><td>五万</td><td>六万</td><td>七万</td><td>八万</td><td>九万</td></tr>
     *  <tr><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
     *  <tr class="fan"><td>一条</td><td>二条</td><td>三条</td><td>四条</td><td>五条</td><td>六条</td><td>七条</td><td>八条</td><td>九条</td></tr>
     *  <tr><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
     *  <tr class="fan"><td>一饼</td><td>二饼</td><td>三饼</td><td>四饼</td><td>五饼</td><td>六饼</td><td>七饼</td><td>八饼</td><td>九饼</td></tr>
     *  <tr><td>31</td><td>32</td><td>33</td><td>34</td><td>35</td><td>36</td><td>37</td><td>38</td><td>39</td></tr>
     *  <tr class="fan"><td>东</td><td>南</td><td>西</td><td>北</td></tr>
     *  <tr><td>41</td><td>42</td><td>43</td><td>44</td></tr>
     *  <tr class="fan"><td>中</td><td>发</td><td>白</td></tr>
     *  <tr><td>51</td><td>52</td><td>53</td></tr>
     *  <tr class="fan"><td>春</td><td>夏</td><td>秋</td><td>冬</td><td>梅</td><td>兰</td><td>竹</td><td>菊</td></tr>
     *  <tr><td>61</td><td>62</td><td>63</td><td>64</td><td>65</td><td>66</td><td>67</td><td>68</td></tr>
     *  </table>
     *  <hr/>
     * 
     * @param pais
     *            一开局发完牌后的牌，不包括刚摸到的牌
     * @param mopaihao
     *            刚摸到的一张牌
     * @param quanfeng
     *            圈风
     * @param menfeng
     *            门风
     */
    Pai(int[] pais, int modao, int quanfeng, int menfeng) {
        if (pais.length != 13)
            throw new MajiangException("构造 Pai 时出错。初始化的牌面必须是 13 张牌。");
        for (int paihao : pais) {
            addPai(paihao, 1);
            if (!Pais.isHua(paihao))
                addPaiType(paihao, 1);
        }
        this.modao = modao;
        this.quanfeng = quanfeng;
        this.menfeng = menfeng;
    }
    
    /**
     * 通过牌的数组构造实例
     * <hr/>
     * <style>table{border-collapse:collapse;}td{border:1px solid green;}.fan{background-color:#BAEB67;}</style>
     * 每张牌的牌号如下：
     *  <table>                                                                                                  
     *  <tr class="fan"><td>一万</td><td>二万</td><td>三万</td><td>四万</td><td>五万</td><td>六万</td><td>七万</td><td>八万</td><td>九万</td></tr>
     *  <tr><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
     *  <tr class="fan"><td>一条</td><td>二条</td><td>三条</td><td>四条</td><td>五条</td><td>六条</td><td>七条</td><td>八条</td><td>九条</td></tr>
     *  <tr><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
     *  <tr class="fan"><td>一饼</td><td>二饼</td><td>三饼</td><td>四饼</td><td>五饼</td><td>六饼</td><td>七饼</td><td>八饼</td><td>九饼</td></tr>
     *  <tr><td>31</td><td>32</td><td>33</td><td>34</td><td>35</td><td>36</td><td>37</td><td>38</td><td>39</td></tr>
     *  <tr class="fan"><td>东</td><td>南</td><td>西</td><td>北</td></tr>
     *  <tr><td>41</td><td>42</td><td>43</td><td>44</td></tr>
     *  <tr class="fan"><td>中</td><td>发</td><td>白</td></tr>
     *  <tr><td>51</td><td>52</td><td>53</td></tr>
     *  <tr class="fan"><td>春</td><td>夏</td><td>秋</td><td>冬</td><td>梅</td><td>兰</td><td>竹</td><td>菊</td></tr>
     *  <tr><td>61</td><td>62</td><td>63</td><td>64</td><td>65</td><td>66</td><td>67</td><td>68</td></tr>
     *  </table>
     *  <hr/>
     * 
     * @param pais
     *            一开局发完牌后的牌，不包括刚摸到的牌
     * @param quanfeng
     *            圈风
     * @param menfeng
     *            门风
     */
    Pai(int[] pais, int quanfeng, int menfeng) {
        this(pais, 0, quanfeng, menfeng);
    }

    /**
     * 摸牌或补牌 <br/>
     * <br/>
     * 指打的过程中的摸牌，一开局时从没有牌到摸到13张时不算
     * 
     * @param paihao
     *            摸到的牌的牌号
     */
    public void mo(int paihao) {
        modao = paihao;
    }

    /**
     * 出牌
     * 
     * @param paihao
     *            出的牌的牌号
     */
    /*
     * 出的牌就是摸到的牌时，modao赋值成0，其余无操作
     * 否则： 出牌牌号对应数量减1 出牌牌号对应类型总数量减1 
     * 摸到的牌对应数量加1 摸到的牌对应类型总数量加1(花牌不加，补花时操作)
     */
    public void chu(int paihao) {
        if (Pais.isHua(paihao))
            throw new MajiangException("无法出牌。花牌不能出。");
        if (paihao == modao) {
            modao = 0;
            return;
        }
        int c = data[paihao];
        if (c == 0)
            throw new MajiangException("\n无法出牌。目前牌面\n" + this + "\n不存在 "
                    + Pais.getPaiName(paihao) + "。");
        addPai(paihao, -1);
        addPaiType(paihao, -1);
        addModao();
    }

    /**
     * 出牌 <br/>
     * <br/>
     * 根据牌在手里的牌中的索引号(从0开始，不包括吃碰杠的牌)出牌
     * 
     * @param index
     *            出的牌在手里的牌中的索引号(从0开始，不包括吃碰杠的牌)
     * @return 出的牌的牌号
     */
    public int chuByIndex(int index) {
        if (index >= 13 - data[1] * 3) {
            int md = modao;
            chu(modao);
            return md;
        }
        int type = 10;
        while (type <= 50 && data[type] < index + 1) {
            index -= data[type];
            type += 10;
        }
        if (type > 50)
            throw new MajiangException("无法出牌。花牌不能出。");
        int p = type + 1;
        while (data[p] < index + 1) {
            index -= data[p];
            p++;
        }
        chu(p);
        return p;
    }

    /**
     * 吃牌 <br/>
     * <br/>
     * 调用前需要确保能够吃这张牌(通过调用 {@link #canChi(int)} 方法返回 非空)<br/>
     * 考虑到效率，此方法内部不再检查是否能够吃这张牌，如果不能吃，会得到不可预期的结果
     * 
     * @param chifa
     *            吃法,应该是调用canChi方法的返回值之一
     */
    /*
     * 吃牌后手里2张牌数量各减1，吃牌类型对应总数量减2，保存吃碰杠数据
     */
    public void chi(Chifa chifa) {
        int chiType = chifa.type;
        int paihao = chifa.paihao;
        switch (chiType) {
            case CHI_LEFT :
                addPai(paihao + 1, -1);
                addPai(paihao + 2, -1);
                addShun(paihao);
                break;
            case CHI_CENTER :
                addPai(paihao - 1, -1);
                addPai(paihao + 1, -1);
                addShun(paihao - 1);
                break;
            case CHI_RIGHT :
                addPai(paihao - 2, -1);
                addPai(paihao - 1, -1);
                addShun(paihao - 2);
                break;
        }
        addPaiType(paihao, -2);
        saveChiPengGang(chifa.type, chifa.paihao);
        moveLastToModao();
    }

    /**
     * 碰牌 <br/>
     * <br/>
     * 调用前需要确保能够碰这张牌(通过调用  {@link #canPeng(RelativeFangwei, int)} 方法返回非空)<br/>
     * 考虑到效率，此方法内部不再检查是否能够碰这张牌，如果不能碰，会得到不可预期的结果
     * 
     * @param pengfa
     *            碰牌的方式，应该是调用canPeng方法的返回值
     */
    /*
     * 碰牌对应数量减2，碰牌类型对应总数量减2，保存吃碰杠数据
     */
    public void peng(Pengfa pengfa) {
        int paihao = pengfa.paihao;
        addPai(paihao, -2);
        addPaiType(paihao, -2);
        saveChiPengGang(pengfa.type, paihao);
        addKe(paihao);
        moveLastToModao();
    }

    /**
     * 杠牌 <br/>
     * <br/>
     * 调用前需要确保能够杠这张牌(通过调用 {@link #canAngang()} 或
     * {@link #canMinggangOther(RelativeFangwei, int)} 或
     * {@link #canMinggangPeng()} 方法返回非空)<br/>
     * 考虑到效率，此方法内部不再检查是否能够杠这张牌，如果不能杠，会得到不可预期的结果<br/>
     * 杠牌后需要补牌，这个摸牌操作需要由外部主动调用(一般是从后面摸)
     * 
     * @param gangfa
     *            杠的方式，应该是调用{@link #canAngang()}或
     *            {@link #canMinggangOther(RelativeFangwei, int)}或
     *            {@link #canMinggangPeng()}方法的返回值
     */
    public void gang(Gangfa gangfa) {
        if (gangfa.type == GANG_AN)
            angang(gangfa);
        else
            minggang(gangfa);
    }

    /**
     * 明杠 <br/>
     * <br/>
     * 调用前需要确保能够明杠这张牌(通过调用 {@link #canMinggangOther(RelativeFangwei, int)} 或
     * {@link #canMinggangPeng()} 方法返回非空)<br/>
     * 考虑到效率，此方法内部不再检查是否能够明杠这张牌，如果不能明杠，会得到不可预期的结果<br/>
     * 杠牌后需要补牌，这个摸牌操作需要由外部主动调用(一般是从后面摸)
     * 
     * @param gangfa
     *            明杠的方式，应该是调用 {@link #canMinggangOther(RelativeFangwei, int)} 或
     *            {@link #canMinggangPeng()} 方法的返回值
     */
    /*
     * 明杠牌对应数量减3，明杠牌类型对应总数量减3，保存吃碰杠数据
     */
    public void minggang(Gangfa gangfa) {
        int paihao = gangfa.paihao;
        int type = gangfa.type;
        if (type != GANG_PENG) {
            addPai(paihao, -3);
            addPaiType(paihao, -3);
            saveChiPengGang(gangfa.type, paihao);
            addKe(paihao);
        } else {
            addModao();
            addPai(paihao, -1);
            addPaiType(paihao, -1);
            for (int i = 2; i <= 8; i += 2) {
                if (data[i + 1] == paihao) {
                    if (data[i] == PENG_XIAJIA) {
                        data[i] = GANG_XIAJIA;
                        break;
                    }
                    if (data[i] == PENG_DUIJIA) {
                        data[i] = GANG_DUIJIA;
                        break;
                    }
                    if (data[i] == PENG_SHANGJIA) {
                        data[i] = GANG_SHANGJIA;
                        break;
                    }
                }
            }
        }
    }

    /**
     * 暗杠 <br/>
     * <br/>
     * 调用前需要确保能够暗杠这张牌(通过调用 {@link #canAngang()} 方法返回 非空)<br/>
     * 考虑到效率，此方法内部不再检查是否能够暗杠这张牌，如果不能暗杠，会得到不可预期的结果<br/>
     * 杠牌后需要补牌，这个摸牌操作需要由外部主动调用(一般是从后面摸)
     * 
     * @param gangfa
     *            暗杠的方式，应该是调用 {@link #canAngang()} 方法的返回值
     */
    /*
     * 暗杠一定发生在摸牌以后，暗杠牌对应数量减4(或摸到的牌暗杠的话减3)，暗杠牌类型对应总数量减4(或摸到的牌暗杠的话减3)，保存吃碰杠数据
     */
    public void angang(Gangfa gangfa) {
        int paihao = gangfa.paihao;
        addModao();
        addPai(paihao, -4);
        addPaiType(paihao, -4);
        saveChiPengGang(GANG_AN, paihao);
        addKe(paihao);
    }

    /**
     * 补花 <br/>
     * <br/>
     * 调用前需要确保能够补花(通过调用 {@link #canBuhua()} 方法返回 true)<br/>
     * 只补一张花，补的是手里第1张花牌(包括摸到的牌,按照春夏秋冬梅兰竹菊的顺序)<br/>
     * 补花后要再摸一张，这个摸牌操作需要由外部主动调用(一般是从后面摸)
     */
    /*
     * 补花的花牌对应位置变为-1，补花总数量加1
     */
    public void buhua() {
        if (!canBuhua())
            throw new MajiangException("\n无法补花。目前牌面\n" + this + "\n不存在花牌。");
        addModao();
        for (int h = 61; h <= 68; h++)
            if (data[h] == 1 || modao == h) {
                data[h] = -1;
                break;
            }
        data[60]++;
    }

    /**
     * 和牌 <br/>
     * <br/>
     * 调用前需要确保能够和这张牌(必须调用 {@link #canHu()} 
     * 或者 {@link #canHu(boolean, boolean, boolean, boolean, boolean)}
     * 或者 {@link #canHu(RelativeFangwei, int)}
     * 或者 {@link #canHu(RelativeFangwei, int, boolean, boolean, boolean, boolean, boolean)}
     * 方法，并且返回true)<br/>
     * 考虑到效率，此方法内部不再检查是否能够和这张牌，如果不能和，会得到不可预期的结果
     */
    public void hu() {
        addPai(hupai.paihao, 1);
        addPaiType(hupai.paihao, 1);
        hupai.huPaixings = paixingAnalyser.analyse(hupai);
        addPai(hupai.paihao, -1);
        addPaiType(hupai.paihao, -1);
        data[0] = hupai.type;
        modao = hupai.paihao;
    }

    /**
     * 判断是否可以吃牌 <br/>
     * <br/>
     * 返回吃牌方式，如果不能吃，返回 Chifa[0]<br/>
     * 
     * @param paihao
     *            需要判断的牌号
     * @return 所有可能的吃牌方式。如果不能吃，返回 Chifa[0]
     */
    public Chifa[] canChi(int paihao) {
        // 不是万条饼，不能吃
        if (!Pais.isWan(paihao) && !Pais.isTiao(paihao) && !Pais.isBing(paihao))
            return new Chifa[0];
        int p = (paihao % 10);
        if (p == 1)
            return canChi1(paihao);
        if (p == 9)
            return canChi9(paihao);
        if (p == 2)
            return canChi2(paihao);
        if (p == 8)
            return canChi8(paihao);
        return canChi34567(paihao);
    }

    /**
     * 判断是否可以碰牌 <br/>
     * <br/>
     * 返回碰牌方式，如果不能碰，返回 null
     * 
     * @param who
     *            碰谁的牌
     * @param paihao
     *            需要判断的牌号
     * 
     * @return 碰牌方式，如果不能碰，返回 null
     */
    public Pengfa canPeng(RelativeFangwei who, int paihao) {
        if (count(paihao) < 2)
            return null;
        return new Pengfa(who, paihao);
    }

    /**
     * 判断是否可以明杠别人的牌 <br/>
     * <br/>
     * 返回明杠牌方式，如果不能明杠，返回 null
     * 
     * @param who
     *            明杠谁的牌
     * @param paihao
     *            需要判断的牌号
     * 
     * @return 明杠牌方式，如果不能明杠，返回 null
     */
    public Gangfa canMinggangOther(RelativeFangwei who, int paihao) {
        if (who == RelativeFangwei.ZIJI)
            throw new MajiangException("此方法不能明杠自己的牌。");
        if (count(paihao) != 3)
            return null;
        return new Gangfa(who, paihao);
    }

    /**
     * 判断是否可以明杠(明杠碰过的牌) <br/>
     * <br/>
     * 返回明杠牌方式，如果不能明杠，返回 Gangfa[0]<br/>
     * 
     * @return 明杠牌方式，如果不能明杠，返回 Gangfa[0]
     */
    /*
     * 这种明杠一定发生在摸牌以后，需要结合摸到的牌判断是否可以杠
     */
    public Gangfa[] canMinggangPeng() {
        if (data[1] == 0)
            return new Gangfa[0];
        int n = 0; // 可以杠的牌有几种
        int[] g = new int[4]; // 可以杠的牌
        for (int i = 2; i <= 8; i+=2)
            if (data[i] == PENG_XIAJIA 
             || data[i] == PENG_DUIJIA 
             || data[i] == PENG_SHANGJIA) {
                if (data[i + 1] == modao)
                    g[n++] = modao;
                else if (count(data[i + 1]) == 1)
                    g[n++] = data[i + 1];
            }
        if (n == 0)
            return new Gangfa[0];
        Gangfa[] r = new Gangfa[n];
        for (int i = 0; i < n; i++)
            r[i] = new Gangfa(RelativeFangwei.ZIJI, g[i]);
        return r;
    }

    /**
     * 判断是否可以暗杠 <br/>
     * <br/>
     * 返回暗杠牌方式，如果不能暗杠，返回 Gangfa[0]<br/>
     * 
     * @param paihao
     *            需要判断的牌号
     * @return 暗杠的方式，如果不能暗杠，返回Gangfa[0]
     */
    /*
     * 暗杠一定发生在摸牌以后，需要结合摸到的牌判断是否可以暗杠
     */
    public Gangfa[] canAngang() {
        if (data[1] >= 4)
            return new Gangfa[0];

        int n = 0; // 可以暗杠的牌有几种
        int[] g = new int[4]; // 可以暗杠的牌

        int b = 0; // 某种牌的张数
        int p = 0; // 扫描到的某张牌的牌号
        int ps = 0; // 扫描到的某张牌的张数

        // 万条饼
        for (int i = 10; i <= 30; i += 10) {
            b = data[i];
            if (b > 2)
                for (int j = 1; j <= 9; j++) {
                    p = i + j;
                    ps = data[p];
                    if (ps == 4 || ps == 3 && modao == p)
                        g[n++] = p;
                }
        }

        // 风
        if (n < 4 && data[40] > 2) {
            for (int j = 1; j <= 4; j++) {
                p = 40 + j;
                ps = data[p];
                if (ps == 4 || ps == 3 && modao == p)
                    g[n++] = p;
            }
        }

        // 箭
        if (n < 4 && data[50] > 2) {
            for (int j = 1; j <= 3; j++) {
                p = 50 + j;
                ps = data[p];
                if (ps == 4 || ps == 3 && modao == p)
                    g[n++] = p;
            }
        }

        if (n == 0)
            return new Gangfa[0];
        Gangfa[] r = new Gangfa[n];
        for (int i = 0; i < n; i++)
            r[i] = new Gangfa(RelativeFangwei.ZIJI, g[i], true);
        return r;
    }

    /**
     * 判断是否可以补花 <br/>
     * <br/>
     * 只要手里有花牌(包括摸到的牌)，就可以补
     * 
     * @return 是否可以补花
     */
    public boolean canBuhua() {
        return Pais.isHua(modao) 
            || data[61] == 1 || data[62] == 1 || data[63] == 1 || data[64] == 1 
            || data[65] == 1 || data[66] == 1 || data[67] == 1 || data[68] == 1;
    }

    /**
     * 判断是否可以和牌
     * <pre>
     * 注意： 
     *     1、采取的原则是先判断特殊牌型，再判断一般规则；
     *     2、canHuBase()不仅判断是否满足基本规则，还会记录手中暗牌的顺刻，这在某些特殊牌型判断中需要用到；
     *     3、当不是特殊牌型时，才会使用canHuBase()返回值。
     *
     * 因为先判断特殊牌型，再判断一般规则，所以当遇到特殊牌型和一般牌型都满足时，需要根据不同牌型的具体分值采用不同的匹配策略：
     *     1、如果特殊牌型分值高，比如同时满足七对(24)和三色双龙会(16)，则不需特殊处理，因为先满足七对后，只要保证七对的后续牌型不包括三色双龙会即可；
     *     2、如果一般牌型分值高，比如同时满足七对(24)和一色双龙会(64)，则需要特殊处理，在七对的匹配方法中，需要判断是否满足一色双龙会，如果满足则不再认为满足七对。
     * </pre>
     * 
     * @param who
     *            和谁的牌
     * @param paihao
     *            要和的牌，如果是自摸，传入刚摸到的牌
     * @param isLast
     *            是否是一局的最后一张牌
     * @param isJuezhang
     *            是否是绝张
     * @param isQianggang
     *            是否是抢杠的牌
     * @param isGangbu
     *            是否是杠牌后补到的牌
     * @param isHuabu
     *            是否是补花后补到的牌
     * 
     * @return 是否可以和牌
     */
    /*
     * 判断结果会在this.hupai中缓存
     */
    public boolean canHu(RelativeFangwei who,
                         int paihao,
                         boolean isLast,
                         boolean isJuezhang,
                         boolean isQianggang,
                         boolean isGangbu,
                         boolean isHuabu) {
        if (Pais.isHua(paihao)) // 是花牌，不可能和
            return false;
        for (int i = 61; i <= 68; i++) // 有花未补，不可能和
            if (data[i] == 1)
                return false;
        addPai(paihao, 1); // 把要和的牌张数加1
        addPaiType(paihao, 1); // 把要和的牌的牌型张数加1
        hupai.reset(who, paihao, isLast, isJuezhang, isQianggang, isGangbu, isHuabu); // 初始化hupai
        resetShunKeWithChiPengGang(); // 吃碰杠的牌记录顺刻
        boolean hu = canHuBase(); // 判断是否满足基本和牌规则(33332形式)
        HuPaixing specialHuPaixing = paixingAnalyser.isSpecialPaixing(hupai); // 解析是否是特殊牌型
        if (specialHuPaixing != null) { // 是特殊牌型
            hu = true;
            hupai.huPaixings = new HuPaixing[] { specialHuPaixing }; // 在hupai中保存匹配的特殊牌型
        }
        addPai(paihao, -1); // 把要和的牌张数刚才加上的1去掉
        addPaiType(paihao, -1); // 把要和的牌的牌型张数刚才加上的1去掉
        return hu;
    }

    /**
     * 判断是否可以和牌<br/>
     * 不是特殊情况，即不是一局最后一张牌，不是绝张，不是抢杠牌，不是杠牌后补的牌，不是补花后补的牌
     * 
     * @param who
     *            和谁的牌
     * @param paihao
     *            要和的牌，如果是自摸，传入刚摸到的牌
     * 
     * @return 是否可以和牌
     */
    public boolean canHu(RelativeFangwei who, int paihao) {
        return canHu(who, paihao, false, false, false, false, false);
    }

    /**
     * 判断是否可以和牌(自摸)
     * 
     * @param isLast
     *            是否是一局的最后一张牌
     * @param isJuezhang
     *            是否是绝张
     * @param isQianggang
     *            是否是抢杠的牌
     * @param isGangbu
     *            是否是杠牌后补到的牌
     * @param isHuabu
     *            是否是补花后补到的牌
     * 
     * @return 是否可以和牌
     */
    public boolean canHu(boolean isLast,
                         boolean isJuezhang,
                         boolean isQianggang,
                         boolean isGangbu,
                         boolean isHuabu) {
        return canHu(RelativeFangwei.ZIJI, modao, isLast, isJuezhang, isQianggang, isGangbu, isHuabu);
    }
    
    /**
     * 判断是否可以和牌(自摸)<br/>
     * 不是特殊情况，即不是一局最后一张牌，不是绝张，不是抢杠牌，不是杠牌后补的牌，不是补花后补的牌
     * 
     * @return 是否可以和牌
     */
    public boolean canHu() {
        return canHu(RelativeFangwei.ZIJI, modao, false, false, false, false, false);
    }

    /**
     * 查找某张牌的张数。 <br/>
     * <br/>
     * 不包含已经吃碰杠的牌和刚摸到的牌
     * 
     * @param paihao
     *            要查找的牌号
     * @return 张数
     */
    public int count(int paihao) {
        return data[paihao];
    }

    /**
     * 是否存在某张牌。 <br/>
     * <br/>
     * 不包含已经吃碰杠的牌和刚摸到的牌
     * 
     * @param paihao
     * @return
     */
    public boolean exists(int paihao) {
        return count(paihao) > 0;
    }
    
    /**
     * 得到刚摸到的牌号
     * 
     * @return 刚摸到的牌号
     */
    public int getModao() {
        return this.modao;
    }

    /**
     * 得到和牌后的牌型 <br/>
     * <br/>
     * 返回null表示还没有和牌
     * 
     * @return 和牌后的牌型，还没有和牌时返回null
     */
    public Hupai getHupai() {
        if (data[0] == 0)
            return null;
        return hupai;
    }

    /**
     * 得到使用的牌型解析器得到
     * 
     * @return 正在使用的牌型解析器
     */
    public PaixingAnalyser getPaixingAnalyser() {
        return paixingAnalyser;
    }

    /**
     * 设置牌型解析器
     * 
     * @param paixingAnalyser
     *            牌型解析器
     */
    public void setPaixingAnalyser(PaixingAnalyser paixingAnalyser) {
        this.paixingAnalyser = paixingAnalyser;
    }
    
    /**
     * 牌面 的字符串表示<br/>
     * <br/>
     * 吃，碰，杠，补花用【】表示，吃的牌用()括起来，碰杠用四个箭头表示是碰杠的哪家的牌，下箭头表示暗杠 <br/>
     * 如果已和牌，最后一张表示和的牌，用四个箭头表示是和的哪家的牌，下箭头表示自摸
     * 
     * @param onlyMingpai
     *            是否只显示明牌(吃碰杠的牌)
     * @return 牌面字符串
     */
    public String toString(boolean onlyMingpai){
        char space = ' ';
        char left = '【';
        char right = '】';

        StringBuilder s = new StringBuilder(100);
        int n = 0;

        // 吃碰杠的牌
        if (data[1] != 0) {
            for (int i = 1; i <= data[1]; i++) {
                n = i << 1;
                int type = data[n];
                if (type == 0)
                    break;
                int paihao = data[n + 1];
                String name = Pais.getPaiName(paihao);
                s.append(left);
                switch (type) {
                    case CHI_LEFT :
                        s.append('(').append(name).append(')')
                         .append(Pais.getPaiName(paihao + 1)).append(space)
                         .append(Pais.getPaiName(paihao + 2));
                        break;
                    case CHI_CENTER :
                        s.append(Pais.getPaiName(paihao - 1)).append('(')
                         .append(name).append(')')
                         .append(Pais.getPaiName(paihao + 1));
                        break;
                    case CHI_RIGHT :
                        s.append(Pais.getPaiName(paihao - 2)).append(space)
                         .append(Pais.getPaiName(paihao - 1)).append('(')
                         .append(name).append(')');
                        break;
                    case PENG_XIAJIA :
                        s.append(RelativeFangwei.XIAJIA).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                    case PENG_DUIJIA :
                        s.append(RelativeFangwei.DUIJIA).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                    case PENG_SHANGJIA :
                        s.append(RelativeFangwei.SHANGJIA).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                    case GANG_XIAJIA :
                        s.append(RelativeFangwei.XIAJIA).append(name)
                         .append(space).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                    case GANG_DUIJIA :
                        s.append(RelativeFangwei.DUIJIA).append(name)
                         .append(space).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                    case GANG_SHANGJIA :
                        s.append(RelativeFangwei.SHANGJIA).append(name)
                         .append(space).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                    case GANG_AN :
                        s.append(RelativeFangwei.ZIJI).append(name)
                         .append(space).append(name)
                         .append(space).append(name)
                         .append(space).append(name);
                        break;
                }
                s.append(right);
            }
        }

        // 补过的花
        n = data[60];
        if (n > 0) {
            s.append(left);
            for (int j = 1; j <= 8; j++)
                if (data[60 + j] == -1) {
                    s.append(Pais.getPaiName(60 + j));
                    if (--n > 0)
                        s.append(space);
                }
            s.append(right);
        }

        if (!onlyMingpai) {
            s.append(space);
            
            // 万条饼
            for (int i = 10; i <= 30; i += 10) {
                n = data[i];
                if (n != 0) {
                    for (int j = 1; j <= 9; j++) {
                        for (int g = 1; g <= data[i + j]; g++)
                            s.append(Pais.getPaiName(i + j)).append(space);
                    }
                }
            }

            // 风
            n = data[40];
            if (n != 0) {
                for (int j = 1; j <= 4; j++) {
                    for (int g = 1; g <= data[40 + j]; g++)
                        s.append(Pais.getPaiName(40 + j)).append(space);
                }
            }

            // 箭
            n = data[50];
            if (n != 0) {
                for (int j = 1; j <= 3; j++) {
                    for (int g = 1; g <= data[50 + j]; g++)
                        s.append(Pais.getPaiName(50 + j)).append(space);
                }
            }

            // 未补的花
            for (int j = 1; j <= 8; j++) {
                if (data[60 + j] == 1)
                    s.append(Pais.getPaiName(60 + j)).append('　').append(space);
            }

            s.append(space).append(space).append(space).append(space);

            // 是否和牌
            switch (data[0]) {
                case HU_XIAJIA :
                    s.append(RelativeFangwei.XIAJIA);
                    break;
                case HU_DUIJIA :
                    s.append(RelativeFangwei.DUIJIA);
                    break;
                case HU_SHANGJIA :
                    s.append(RelativeFangwei.SHANGJIA);
                    break;
                case HU_ZIJI :
                    s.append(RelativeFangwei.ZIJI);
                    break;
            }

            // 摸到的牌
            if (modao > 0)
                s.append(Pais.getPaiName(modao));
        }
        
        if (data[0] != 0) {
            int sum = 0;
            for (HuPaixing hp : hupai.getHuPaixings()) {
                sum += hp.paixing.getFanshu() * hp.count;
                s.append("\n\t" + hp);
            }
            s.append("\n        共：" + sum + " 番");
        }

        return s.toString();
    }

    /**
     * 以字符串表示的牌面 <br/>
     * <br/>
     * 吃，碰，杠，补花用【】表示，吃的牌用()括起来，碰杠用四个箭头表示是碰杠的哪家的牌，下箭头表示暗杠 <br/>
     * 如果已和牌，最后一张表示和的牌，用四个箭头表示是和的哪家的牌，下箭头表示自摸
     * 
     * @return 牌面字符串
     */
    @Override
    public String toString() {
        return toString(false);
    }

    /*
     * 吃 1，返回吃法
     */
    private Chifa[] canChi1(int paihao) {
        boolean b2 = exists(paihao + 1); // 找到了 2
        boolean b3 = exists(paihao + 2); // 找到了 3
        if (b2 && b3)
            return new Chifa[] { new Chifa(CHI_LEFT, paihao) };
        return new Chifa[0];
    }

    /*
     * 吃 9，返回吃法
     */
    private Chifa[] canChi9(int paihao) {
        boolean b7 = exists(paihao - 2); // 找到了 7
        boolean b8 = exists(paihao - 1); // 找到了 8
        if (b7 && b8)
            return new Chifa[] { new Chifa(CHI_RIGHT, paihao) };
        return new Chifa[0];
    }

    /*
     * 吃 2，返回吃法
     */
    private Chifa[] canChi2(int paihao) {
        boolean b1 = exists(paihao - 1); // 找到了 1
        boolean b3 = exists(paihao + 1); // 找到了 3
        boolean b4 = exists(paihao + 2); // 找到了 4
        if (b1 && b3 && !b4)
            return new Chifa[] { new Chifa(CHI_CENTER, paihao) };
        if (!b1 && b3 && b4)
            return new Chifa[] { new Chifa(CHI_LEFT, paihao) };
        if (b1 && b3 && b4)
            return new Chifa[] { new Chifa(CHI_CENTER, paihao),
                                 new Chifa(CHI_LEFT, paihao) };
        return new Chifa[0];
    }

    /*
     * 吃 8，返回吃法
     */
    private Chifa[] canChi8(int paihao) {
        boolean b6 = exists(paihao - 2); // 找到了 6
        boolean b7 = exists(paihao - 1); // 找到了 7
        boolean b9 = exists(paihao + 1); // 找到了 9
        if (b6 && b7 && !b9)
            return new Chifa[] { new Chifa(CHI_RIGHT, paihao) };
        if (!b6 && b7 && b9)
            return new Chifa[] { new Chifa(CHI_CENTER, paihao) };
        if (b6 && b7 && b9)
            return new Chifa[] { new Chifa(CHI_RIGHT, paihao),
                                 new Chifa(CHI_CENTER, paihao) };
        return new Chifa[0];
    }

    /*
     * 吃34567，返回吃法
     */
    private Chifa[] canChi34567(int paihao) {
        boolean b1 = exists(paihao - 2); // 前两张
        boolean b2 = exists(paihao - 1); // 前一张
        boolean b3 = exists(paihao + 1); // 后一张
        boolean b4 = exists(paihao + 2); // 后两张
        if (b1 && b2 && !b3)
            return new Chifa[] { new Chifa(CHI_RIGHT, paihao) };
        if (!b1 && b2 && b3 && !b4)
            return new Chifa[] { new Chifa(CHI_CENTER, paihao) };
        if (!b2 && b3 && b4)
            return new Chifa[] { new Chifa(CHI_LEFT, paihao) };
        if (b1 && b2 && b3 && !b4)
            return new Chifa[] { new Chifa(CHI_RIGHT, paihao),
                                 new Chifa(CHI_CENTER, paihao) };
        if (!b1 && b2 && b3 && b4)
            return new Chifa[] { new Chifa(CHI_CENTER, paihao),
                                 new Chifa(CHI_LEFT, paihao) };
        if (b1 && b2 && b3 && b4)
            return new Chifa[] { new Chifa(CHI_RIGHT, paihao),
                                 new Chifa(CHI_CENTER, paihao),
                                 new Chifa(CHI_LEFT, paihao) };
        return new Chifa[0];
    }

    /*
     * 判断是否满足基本和牌规则，即33332类型；还会找出将牌牌号，并记录顺刻
     * 判断的是手里的暗牌
     * 
     * 调用前已将判断是否能和的牌放入data数组
     * 
     * 按下列步骤判断：
     * 1 如果某种牌总数mod3为1，不能和
     * 2 如果余数为2，将牌一定位于此种牌内，并且其它类型余数只能为0 
     * 3 判断每个类型
     *      如果是将牌所在类型，找出将牌保存在45位置，并将其余牌根据顺刻在46~49位置保存顺子的牌号，在54~57位置保存刻牌的牌号
     *      如果不是将牌所在类型，根据顺刻在46~49位置保存顺子的牌号，在54~57位置保存刻牌的牌号
     */
    private boolean canHuBase() {
        int jiangType = 0;
        int type = 10;
        int p = 0;

        for (type = 10; type <= 50; type += 10) {
            p = (data[type] % 3);
            if (p == 1)
                return false;
            if (p == 2) {
                if (jiangType != 0)
                    return false;
                jiangType = type;
            }
        }

        if (jiangType == 0)
            return false;

        for (type = 10; type <= 50; type += 10)
            if (type != jiangType && !isShunKe(type)
             || type == jiangType && !isShunKeJiang(type))
                return false;
        
        return true;
    }

    /*
     * 判断某种类型的牌是否都是顺和刻
     * 参数type是 10,20,30,40,50 之一，代表万条饼风箭
     * 判断时，先按照刻推测，再按照顺推测。这也基本满足现实情况，因为既能看成刻又能看成顺时，一般看成刻的分数高(但是有特殊情况，比如一色四同顺，这需要在其匹配方法中特殊处理)
     * 执行完毕后，如果满足，则在data的46~49位置保存顺子的牌号，在54~57位置保存刻牌的牌号
     * 
     * 如果是风箭，直接判断；
     * 如果是万条饼，按照下面的步骤：
     *      1 如果此类型没有牌，返回true 
     *      2 从第1张此类型的牌开始判断 
     *      3 如果张数大于等于3，一定含有一个刻，去掉3张，递归判断剩下的 
     *      4 如果张数不足3张，一定是顺，此牌和后续2张牌都去掉1张，递归判断剩下的
     */
    private boolean isShunKe(int type){
        if (type == 40) {
            boolean ok = ((data[41] == 0 || data[41] == 3) 
                       && (data[42] == 0 || data[42] == 3)
                       && (data[43] == 0 || data[43] == 3) 
                       && (data[44] == 0 || data[44] == 3));
            if (ok)
                for (int i = 41; i <= 44; i++)
                    if (data[i] == 3)
                        addKe(i);
            return ok;
        }
        
        if (type == 50) {
            boolean ok = ((data[51] == 0 || data[51] == 3) 
                       && (data[52] == 0 || data[52] == 3) 
                       && (data[53] == 0 || data[53] == 3));
            if (ok)
                for (int i = 51; i <= 53; i++)
                    if (data[i] == 3)
                        addKe(i);
            return ok;
        }
        
        if (data[type] == 0)
            return true;
        
        int p = type + 1;
        while (p <= type + 9) {
            if (data[p] != 0)
                break;
            p++;
        }

        boolean is = false;
        if (data[p] >= 3) {
            data[p] -= 3;
            data[type] -= 3;
            is = isShunKe(type);
            data[p] += 3;
            data[type] += 3;
            if (is)
                addKe(p);
            return is;
        }

        if (data[p] <= 2 && p <= type + 7 && data[p + 1] > 0 && data[p + 2] > 0) {
            data[p]--;
            data[p + 1]--;
            data[p + 2]--;
            data[type] -= 3;
            is = isShunKe(type);
            data[p]++;
            data[p + 1]++;
            data[p + 2]++;
            data[type] += 3;
            if (is)
                addShun(p);
            return is;
        }

        return false;
    }



    /*
     * 判断某种类型的牌是否满足：一个将，其余都是顺和刻
     * 参数type是 10,20,30,40,50 之一，代表万条饼风箭
     * 判断时，先按照刻推测，再按照顺推测。这也基本满足现实情况，因为既能看成刻又能看成顺时，一般看成刻的分数高(但是有特殊情况，比如一色四同顺，这需要在其匹配方法中特殊处理)
     * 执行完毕后，如果满足，则在data的45位置保存将牌牌号，46~49位置保存顺子的牌号，在54~57位置保存刻牌的牌号
     * 
     * 如果是风箭，直接判断；
     * 如果是万条饼，按照下面的步骤：
     *      1 如果此类型没有牌，返回true 
     *      2 从第1张此类型的牌开始判断 
     *      3 如果张数大于等于3，认为含有一个刻，去掉3张，递归判断剩下的 
     *      4 如果张数大于等于2，认为含有将，去掉2张，递归判断剩下的
     *      5 如果张数等于1，认为是顺，此牌和后续2张牌都去掉1张，递归判断剩下的
     */
    private boolean isShunKeJiang(int type){
        if (type == 40) {
            if       (data[41] == 2 
                  && (data[42] == 0 || data[42] == 3)
                  && (data[43] == 0 || data[43] == 3)
                  && (data[44] == 0 || data[44] == 3))
                data[45] = 41;
            else if ((data[41] == 0 || data[41] == 3)
                  &&  data[42] == 2
                  && (data[43] == 0 || data[43] == 3)
                  && (data[44] == 0 || data[44] == 3))
                data[45] = 42;
            else if ((data[41] == 0 || data[41] == 3)
                  && (data[42] == 0 || data[42] == 3)
                  &&  data[43] == 2
                  && (data[44] == 0 || data[44] == 3))
                data[45] = 43;
            else if ((data[41] == 0 || data[41] == 3)
                  && (data[42] == 0 || data[42] == 3)
                  && (data[43] == 0 || data[43] == 3)
                  &&  data[44] == 2)
                data[45] = 44;
            if (data[45] == 0)
                return false;
            for (int i = 41; i <= 44; i++)
                if (data[i] == 3)
                    addKe(i);
            return true;
        }
        
        if (type == 50) {
            if       (data[51] == 2 
                  && (data[52] == 0 || data[52] == 3)
                  && (data[53] == 0 || data[53] == 3))
                data[45] = 51;
            else if ((data[51] == 0 || data[51] == 3) 
                  &&  data[52] == 2
                  && (data[53] == 0 || data[53] == 3))
                data[45] = 52;
            else if ((data[51] == 0 || data[51] == 3)
                  && (data[52] == 0 || data[52] == 3)
                  &&  data[53] == 2)
                data[45] = 53;
            if (data[45] == 0)
                return false;
            for (int i = 51; i <= 53; i++)
                if (data[i] == 3)
                    addKe(i);
            return true;
        }
        
        if (data[type] == 0)
            return true;
        
        int p = type + 1;
        while (p <= type + 9) {
            if (data[p] != 0)
                break;
            p++;
        }

        boolean is = false;
        if (data[p] >= 3) {
            data[p] -= 3;
            data[type] -= 3;
            is = isShunKeJiang(type);
            data[p] += 3;
            data[type] += 3;
            if (is) {
                addKe(p);
                return true;
            }
        }
        
        if (data[p] >= 2) {
            data[p] -= 2;
            data[type] -= 2;
            is = isShunKe(type);
            data[p] += 2;
            data[type] += 2;
            if (is) {
                data[45] = p;
                return true;
            }
        }

        if (p <= type + 7 && data[p + 1] > 0 && data[p + 2] > 0) {
            data[p]--;
            data[p + 1]--;
            data[p + 2]--;
            data[type] -= 3;
            is = isShunKeJiang(type);
            data[p]++;
            data[p + 1]++;
            data[p + 2]++;
            data[type] += 3;
            if (is)
                addShun(p);
            return is;
        }

        return false;
    }

    /*
     * 牌号对应的数量加count
     */
    private void addPai(int paihao, int count) {
        data[paihao] += count;
    }

    /*
     * 牌号对应的类型总数加count(花牌不加，补花后再加)
     */
    private void addPaiType(int paihao, int count){
        data[paihao - paihao % 10] += count;
    }

    /*
     * 将摸到的牌加入data 
     */
    private void addModao() {
        addPai(modao, 1);
        if (!Pais.isHua(modao))
            addPaiType(modao, 1);
        modao = 0;
    }
    
    /*
     * 把顺子的牌号(第1张)存入data的46~49位置
     * 正序排列(0放在最后)
     */
    private void addShun(int shunFirstPaihao) {
        for (int i = 46; i <= 49; i++) {
            if (data[i] == 0) {
                data[i] = shunFirstPaihao;
                return;
            }
            if (data[i] > shunFirstPaihao) {
                System.arraycopy(data, i, data, i + 1, 49 - i);
                data[i] = shunFirstPaihao;
                return;
            }
        }
    }
    
    /*
     * 把刻牌的牌号存入data的54~57位置
     * 正序排列(0放在最后)
     */
    private void addKe(int kePaihao){
        for (int i = 54; i <= 57; i++) {
            if (data[i] == 0) {
                data[i] = kePaihao;
                return;
            }
            if (data[i] > kePaihao) {
                System.arraycopy(data, i, data, i + 1, 57 - i);
                data[i] = kePaihao;
                return;
            }
        }
    }
    
    /*
     * 保存吃碰杠数据
     * 
     * type是类型，应该是 
     *      CHI_LEFT 
     *      CHI_CENTER 
     *      CHI_RIGHT 
     *      PENG_XIAJIA 
     *      PENG_DUIJIA
     *      PENG_SHANGJIA 
     *      GANG_XIAJIA 
     *      GANG_DUIJIA 
     *      GANG_SHANGJIA 
     *      GANG_AN
     * 之一，
     * paihao是吃碰杠的牌号
     */
    private void saveChiPengGang(int type, int paihao) {
        int cpg = data[1];
        data[(cpg << 1) + 2] = type;
        data[(cpg << 1) + 3] = paihao;
        data[1]++;
    }
    
    /*
     * data中 46~49、54~57位置只填充吃碰杠的数据
     */
    private void resetShunKeWithChiPengGang(){
        data[46] = 0;
        data[47] = 0;
        data[48] = 0;
        data[49] = 0;
        data[54] = 0;
        data[55] = 0;
        data[56] = 0;
        data[57] = 0;
        if (data[1] == 0)
            return;
        int s = 46;
        int k = 54;
        for (int i = 2; i <= 8; i += 2) {
            int p = data[i + 1];
            switch (data[i]) {
                case 0 :
                    return;
                case CHI_LEFT :
                    data[s++] = p;
                    break;
                case CHI_CENTER :
                    data[s++] = (p - 1);
                    break;
                case CHI_RIGHT :
                    data[s++] = (p - 2);
                    break;
                case PENG_XIAJIA :
                case PENG_DUIJIA :
                case PENG_SHANGJIA :
                case GANG_XIAJIA :
                case GANG_DUIJIA :
                case GANG_SHANGJIA :
                case GANG_AN :
                    data[k++] = p;
                    break;
            }
        }
    }

    /*
     * 将最后一张牌移到modao的牌上，吃碰后并且出牌之前，需要执行此操作
     */
    private void moveLastToModao() {
        // 花
        int p = 68;
        while (p >= 61)
            if (data[p] != 1)
                p--;
            else {
                data[p] = 0;
                modao = p;
                return;
            }

        // 箭
        if (data[50] != 0) {
            p = 53;
            while (p >= 51)
                if (data[p] == 0)
                    p--;
                else {
                    data[p]--;
                    data[p - p % 10]--;
                    modao = p;
                    return;
                }
        }

        // 风
        if (data[40] != 0) {
            p = 44;
            while (p >= 41)
                if (data[p] == 0)
                    p--;
                else {
                    data[p]--;
                    data[p - p % 10]--;
                    modao = p;
                    return;
                }
        }

        // 饼条万
        for (int i = 30; i >= 10; i -= 10)
            if (data[i] != 0) {
                p = i + 9;
                while (p > i)
                    if (data[p] == 0)
                        p--;
                    else {
                        data[p]--;
                        data[p - p % 10]--;
                        modao = p;
                        return;
                    }
            }
    }

    /*
     * 得到全部牌的拷贝
     */
    static int[] getAllPaiCopy() {
        int length = ALL_PAI.length;
        int[] copy = new int[length];
        System.arraycopy(ALL_PAI, 0, copy, 0, length);
        return copy;
    }

    /**
     * 吃牌时的方式，吃法
     */
    public static class Chifa {

        /*
         * 应该是 
         *      CHI_LEFT 
         *      CHI_CENTER 
         *      CHI_RIGHT 
         * 之一
         */
        private int   type;

        /*
         * 吃的牌号
         */
        private int   paihao;

        /*
         * 牌面，如吃后 三万 四万 五万 表示成数组 {13, 14, 15}
         */
        private int[] pais;

        /*
         * type应该是 
         *      CHI_LEFT 
         *      CHI_CENTER 
         *      CHI_RIGHT 
         * 之一 ，
         * paihao 是吃的牌号
         */
        private Chifa(int type, int paihao) {
            this.type = type;
            this.paihao = paihao;
            switch (type) {
                case CHI_LEFT :
                    pais = new int[] { paihao, paihao + 1, paihao + 2 };
                    break;
                case CHI_CENTER :
                    pais = new int[] { paihao - 1, paihao, paihao + 1 };
                    break;
                case CHI_RIGHT :
                    pais = new int[] { paihao - 2, paihao - 1, paihao };
                    break;
            }
        }

        /**
         * 吃牌类型
         * <pre>
         * 是 
         *      {@link #CHI_LEFT}
         *      {@link #CHI_CENTER}
         *      {@link #CHI_RIGHT}
         * 之一
         * </pre>
         * 
         * @return 吃牌类型
         */
        public int getType() {
            return type;
        }

        /**
         * 吃的牌号
         * 
         * @return 吃的牌号
         */
        public int getPaihao() {
            return paihao;
        }

        /**
         * 得到吃牌构成的3张牌，如 三万 四万 五万 表示成数组 {13, 14, 15}
         * 
         * @return 吃牌构成的3张牌
         */
        public int[] getPais() {
            return pais;
        }
    }

    /**
     * 碰牌时的方式，碰法
     */
    public static class Pengfa {

        /*
         * 应该是 
         *      PENG_XIAJIA 
         *      PENG_DUIJIA 
         *      PENG_SHANGJIA 
         * 之一
         */
        private int type;
        
        /*
         * 碰的谁的牌
         */
        private RelativeFangwei who;

        /*
         * 碰的牌号
         */
        private int paihao;

        /*
         * who 是碰哪一家的牌，paihao 是碰的牌号
         */
        private Pengfa(RelativeFangwei who, int paihao) {
            switch (who) {
                case XIAJIA :
                    type = PENG_XIAJIA;
                    break;
                case DUIJIA :
                    type = PENG_DUIJIA;
                    break;
                case SHANGJIA :
                    type = PENG_SHANGJIA;
                    break;
                default :
                    break;
            }
            this.who = who;
            this.paihao = paihao;
        }

        /**
         * 碰牌类型
         * <pre>
         * 是
         *      {@link #PENG_XIAJIA}
         *      {@link #PENG_DUIJIA}
         *      {@link #PENG_SHANGJIA}
         * 之一
         * </pre>
         * 
         * @return 碰牌类型
         */
        public int getType() {
            return type;
        }
        
        /**
         * 碰的谁的牌
         * 
         * @return 方位
         */
        public RelativeFangwei who() {
            return who;
        }

        /**
         * 碰的牌号
         * 
         * @return 碰的牌号
         */
        public int getPaihao() {
            return paihao;
        }
    }

    /**
     * 杠牌时的方式，杠法
     */
    public static class Gangfa {

        /*
         * 应该是 
         *      GANG_XIAJIA 
         *      GANG_DUIJIA 
         *      GANG_SHANGJIA 
         *      GANG_PENG
         *      GANG_AN 
         * 之一
         */
        private int type;
        
        /*
         * 杠的谁的牌
         */
        private RelativeFangwei who;

        /*
         * 杠的牌号
         */
        private int paihao;

        /*
         * who 是杠哪一家的牌，paihao 杠的牌号 ，isAn 是否暗杠
         */
        private Gangfa(RelativeFangwei who, int paihao, boolean isAn) {
            switch (who) {
                case XIAJIA :
                    type = GANG_XIAJIA;
                    break;
                case DUIJIA :
                    type = GANG_DUIJIA;
                    break;
                case SHANGJIA :
                    type = GANG_SHANGJIA;
                    break;
                case ZIJI :
                    if (isAn)
                        type = GANG_AN;
                    else
                        type = GANG_PENG;
                    break;
            }
            this.who = who;
            this.paihao = paihao;
        }

        private Gangfa(RelativeFangwei who, int paihao) {
            this(who, paihao, false);
        }

        /**
         * 杠牌类型
         * <pre>
         * 是 
         *      {@link #GANG_XIAJIA}
         *      {@link #GANG_DUIJIA}
         *      {@link #GANG_SHANGJIA}
         *      {@link #GANG_PENG}
         *      {@link #GANG_AN}
         * 之一
         * </pre>
         * 
         * @return 杠牌类型
         */
        public int getType() {
            return type;
        }
        
        /**
         * 杠的谁的牌
         * 
         * @return 方位
         */
        public RelativeFangwei who() {
            return who;
        }

        /**
         * 杠的牌号
         * 
         * @return 杠的牌号
         */
        public int getPaihao() {
            return paihao;
        }
    }

    /**
     * 封装和牌的信息<br/>
     * 主要是一些辅助牌型解析的数据和方法
     */
    public class Hupai {
        
        private Hupai() {
            //
        }
        
        /*
         * 应该是 
         *     HU_XIAJIA
         *     HU_DUIJIA 
         *     HU_SHANGJIA 
         *     HU_ZIJI 
         * 之一
         */
        private int        type;
        
        /*
         * 和的牌号
         */
        private int        paihao;
        
        /*
         * 加上吃碰杠后的牌数据
         */
        private int[]      fullData;
        
        /*
         * fullData是否是有效数据
         */
        private boolean     fullDataOk;
        
        /*
         * 是否是一局的最后一张牌
         */
        private boolean     isLast;
        
        /*
         * 是否绝张
         */
        private boolean     isJuezhang;
        
        /*
         * 是否是抢杠
         */
        private boolean     isQianggang;
        
        /*
         * 是否杠牌后补到的牌
         */
        private boolean     isGangbu;
        
        /*
         * 是否补花后补到的牌
         */
        private boolean     isHuabu;
        
        /*
         * 和牌后的牌型
         */
        private HuPaixing[] huPaixings = new HuPaixing[0];

        /**
         * 判断是否满足基本和牌规则，即33332类型
         * 
         * 调用前需要将判断是否能和的牌放入data数组
         * 
         * 执行完毕后，如果满足，则在data的45位置保存将牌牌号，在46~49位置保存顺子的牌号，在54~57位置保存刻牌的牌号
         * 
         * @return 是否满足基本和牌规则
         */
        public boolean canHuBase() {
            return Pai.this.canHuBase();
        }

        /**
         * 和牌类型
         * <pre>
         * 是
         *      {@link #HU_XIAJIA}
         *      {@link #HU_DUIJIA}
         *      {@link #HU_SHANGJIA}
         *      {@link #HU_ZIJI}
         * 之一
         * </pre>
         * 
         * @return 和牌类型
         */
        public int getType() {
            return type;
        }

        /**
         * 和的牌号
         * 
         * @return 和的牌号
         */
        public int getPaihao() {
            return paihao;
        }

        /**
         * 牌的数据(包括要和的牌)
         * 
         * <pre>
         * 存储的结构分为12部分，分别为：
         * <hr/>
         *   0,                            // 位置 0     : 和牌类型
         *   0, 0, 0, 0, 0, 0, 0, 0, 0,    // 位置 1~9   : 吃碰杠
         *   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 10~19 : 万
         *   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 20~29 : 条
         *   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 30~39 : 饼
         *   0, 0, 0, 0, 0,                // 位置 40~44 : 风
         *   0,                            // 位置 45    : 将牌牌号
         *   0, 0, 0, 0,                   // 位置 46~49 : 顺子
         *   0, 0, 0, 0,                   // 位置 50~53 : 箭
         *   0, 0, 0, 0,                   // 位置 54~57 : 刻
         *   0, 0,                         // 位置 58~59 : 空闲，留用
         *   0, 0, 0, 0, 0, 0, 0, 0, 0     // 位置 60~68 : 花
         * <hr/>
         * 
         * 具体内容：
         * 
         * 1 位置0
         *      和牌类型
         *          未和牌：0
         *          和下家：{@link #HU_XIAJIA}
         *          和对家：{@link #HU_DUIJIA}
         *          和上家：{@link #HU_SHANGJIA}
         *          自摸  ：{@link #HU_ZIJI}
         * 
         * 2 位置1~9
         *      存放吃碰杠的牌
         *      其中位置1存放吃碰杠的次数，从第2个元素开始按照发生时间顺序存放每次吃碰杠的表示
         *      每次吃碰杠表示为2个元素：类型和牌号
         *          吃到的牌表示为(放的是吃到的牌，不是最小的牌)
         *              吃到的牌吃后放在左边：{@link #CHI_LEFT}   吃的牌号
         *              吃到的牌吃后放在中间：{@link #CHI_CENTER} 吃的牌号
         *              吃到的牌吃后放在右边：{@link #CHI_RIGHT}  吃的牌号
         *          碰到的牌 表示为
         *              碰下家：{@link #PENG_XIAJIA}   碰的牌号
         *              碰对家：{@link #PENG_DUIJIA}   碰的牌号
         *              碰上家：{@link #PENG_SHANGJIA} 碰的牌号
         *          杠到的牌 表示为
         *              明杠下家：{@link #GANG_XIAJIA}   明杠的牌号
         *              明杠对家：{@link #GANG_DUIJIA}   明杠的牌号
         *              明杠上家：{@link #GANG_SHANGJIA} 明杠的牌号
         *              暗杠    ：{@link #GANG_AN}       暗杠的牌号
         *              (碰后再明杠也表示为前三种明杠)
         * 
         * 3 位置10~19
         *      存放手里的万牌(吃碰杠后剩下的牌)
         *      位置10存放万牌的总数 位置11~19分别存放1到9万的数量
         * 
         * 4 位置20~29
         *      存放手里的条牌(吃碰杠后剩下的牌)
         *      位置20存放条牌的总数 位置21~29分别存放1到9条的数量
         * 
         * 5 位置30~39
         *      存放手里的饼牌(吃碰杠后剩下的牌)
         *      位置30存放饼牌的总数 位置31~39分别存放1到9饼的数量
         * 
         * 6 位置40~44
         *      存放手里的风牌(吃碰杠后剩下的牌)
         *      位置40存放风牌的总数 位置41~44分别存放东风，南风，西风，北风的数量
         * 
         * 7 位置45
         *      判断符合基本和牌形式时，将牌的牌号，特殊牌型此位置数据无意义
         * 
         * 8 位置46~49
         *      判断符合基本和牌形式(33332形式)时，顺子的牌号(第1张)，正序排列。特殊牌型此数据只保存吃的顺
         * 
         * 9 位置50~53
         *      存放手里的箭牌(吃碰杠后剩下的牌)
         *      位置50存放箭牌的总数 位置51~53分别存放红中，发财，白板的数量
         * 
         * 10 位置54~57
         *      判断符合基本和牌形式(33332形式)时，刻牌的牌号，正序排列。特殊牌型此数据只保存碰杠的刻
         * 
         * 11 位置58~59
         *      空闲，留用
         * 
         * 12 位置60~68
         *      存放手里的花牌(包括补过的和未补的)
         *      位置60存放补过的花牌的数量(只是补过的)
         *      位置61~68分别对应春夏秋冬梅兰竹菊8张花牌，未摸到表示为0，摸到了表示为1，摸到并补花了表示为-1
         * 
         * <hr/>
         * <style>table{border-collapse:collapse;}td{border:1px solid green;}.fan{background-color:#BAEB67;}</style>
         * 每张牌的牌号如下：
         *  <table>                                                                                                  
         *  <tr class="fan"><td>一万</td><td>二万</td><td>三万</td><td>四万</td><td>五万</td><td>六万</td><td>七万</td><td>八万</td><td>九万</td></tr>
         *  <tr><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
         *  <tr class="fan"><td>一条</td><td>二条</td><td>三条</td><td>四条</td><td>五条</td><td>六条</td><td>七条</td><td>八条</td><td>九条</td></tr>
         *  <tr><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
         *  <tr class="fan"><td>一饼</td><td>二饼</td><td>三饼</td><td>四饼</td><td>五饼</td><td>六饼</td><td>七饼</td><td>八饼</td><td>九饼</td></tr>
         *  <tr><td>31</td><td>32</td><td>33</td><td>34</td><td>35</td><td>36</td><td>37</td><td>38</td><td>39</td></tr>
         *  <tr class="fan"><td>东</td><td>南</td><td>西</td><td>北</td></tr>
         *  <tr><td>41</td><td>42</td><td>43</td><td>44</td></tr>
         *  <tr class="fan"><td>中</td><td>发</td><td>白</td></tr>
         *  <tr><td>51</td><td>52</td><td>53</td></tr>
         *  <tr class="fan"><td>春</td><td>夏</td><td>秋</td><td>冬</td><td>梅</td><td>兰</td><td>竹</td><td>菊</td></tr>
         *  <tr><td>61</td><td>62</td><td>63</td><td>64</td><td>65</td><td>66</td><td>67</td><td>68</td></tr>
         *  </table>
         *  <hr/>
         * </pre>
         * 
         * @return 牌数据
         */
        /*
         * 返回的是核心数据 Pai.this.data
         * 如果被外部修改，将得到无法预期的结果
         * 由于此数组较大，考虑到效率和空间，没有采用提供此数组拷贝的方式
         */
        public int[] getData() {
            return data;
        }

        /**
         * 牌的数据(包括吃碰杠后的牌、要和的牌，杠的牌也表示为3张)
         * 
         * <pre>
         * 存储的结构分为12部分，分别为：
         * <hr/>
         *   0,                            // 位置 0     : 和牌类型
         *   0, 0, 0, 0, 0, 0, 0, 0, 0,    // 位置 1~9   : 吃碰杠
         *   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 10~19 : 万
         *   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 20~29 : 条
         *   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 位置 30~39 : 饼
         *   0, 0, 0, 0, 0,                // 位置 40~44 : 风
         *   0,                            // 位置 45    : 将牌牌号
         *   0, 0, 0, 0,                   // 位置 46~49 : 顺子
         *   0, 0, 0, 0,                   // 位置 50~53 : 箭
         *   0, 0, 0, 0,                   // 位置 54~57 : 刻
         *   0, 0,                         // 位置 58~59 : 空闲，留用
         *   0, 0, 0, 0, 0, 0, 0, 0, 0     // 位置 60~68 : 花
         * <hr/>
         * 
         * 具体内容：
         * 
         * 1 位置0
         *      和牌类型
         *          未和牌：0
         *          和下家：{@link #HU_XIAJIA}
         *          和对家：{@link #HU_DUIJIA}
         *          和上家：{@link #HU_SHANGJIA}
         *          自摸  ：{@link #HU_ZIJI}
         * 
         * 2 位置1~9
         *      存放吃碰杠的牌
         *      其中位置1存放吃碰杠的次数，从第2个元素开始按照发生时间顺序存放每次吃碰杠的表示
         *      每次吃碰杠表示为2个元素：类型和牌号
         *          吃到的牌表示为(放的是吃到的牌，不是最小的牌)
         *              吃到的牌吃后放在左边：{@link #CHI_LEFT}   吃的牌号
         *              吃到的牌吃后放在中间：{@link #CHI_CENTER} 吃的牌号
         *              吃到的牌吃后放在右边：{@link #CHI_RIGHT}  吃的牌号
         *          碰到的牌 表示为
         *              碰下家：{@link #PENG_XIAJIA}   碰的牌号
         *              碰对家：{@link #PENG_DUIJIA}   碰的牌号
         *              碰上家：{@link #PENG_SHANGJIA} 碰的牌号
         *          杠到的牌 表示为
         *              明杠下家：{@link #GANG_XIAJIA}   明杠的牌号
         *              明杠对家：{@link #GANG_DUIJIA}   明杠的牌号
         *              明杠上家：{@link #GANG_SHANGJIA} 明杠的牌号
         *              暗杠    ：{@link #GANG_AN}       暗杠的牌号
         *              (碰后再明杠也表示为前三种明杠)
         * 
         * 3 位置10~19
         *      存放手里的万牌
         *      位置10存放万牌的总数 位置11~19分别存放1到9万的数量
         * 
         * 4 位置20~29
         *      存放手里的条牌
         *      位置20存放条牌的总数 位置21~29分别存放1到9条的数量
         * 
         * 5 位置30~39
         *      存放手里的饼牌
         *      位置30存放饼牌的总数 位置31~39分别存放1到9饼的数量
         * 
         * 6 位置40~44
         *      存放手里的风牌
         *      位置40存放风牌的总数 位置41~44分别存放东风，南风，西风，北风的数量
         * 
         * 7 位置45
         *      判断符合基本和牌形式时，将牌的牌号，特殊牌型此位置数据无意义
         * 
         * 8 位置46~49
         *      判断符合基本和牌形式(33332形式)时，顺子的牌号(第1张)，正序排列。特殊牌型此数据只保存吃的顺
         * 
         * 9 位置50~53
         *      存放手里的箭牌
         *      位置50存放箭牌的总数 位置51~53分别存放红中，发财，白板的数量
         * 
         * 10 位置54~57
         *      判断符合基本和牌形式(33332形式)时，刻牌的牌号，正序排列。特殊牌型此数据只保存碰杠的刻
         * 
         * 11 位置58~59
         *      空闲，留用
         * 
         * 12 位置60~68
         *      存放手里的花牌(包括补过的和未补的)
         *      位置60存放补过的花牌的数量(只是补过的)
         *      位置61~68分别对应春夏秋冬梅兰竹菊8张花牌，未摸到表示为0，摸到了表示为1，摸到并补花了表示为-1
         * 
         * <hr/>
         * <style>table{border-collapse:collapse;}td{border:1px solid green;}.fan{background-color:#BAEB67;}</style>
         * 每张牌的牌号如下：
         *  <table>                                                                                                  
         *  <tr class="fan"><td>一万</td><td>二万</td><td>三万</td><td>四万</td><td>五万</td><td>六万</td><td>七万</td><td>八万</td><td>九万</td></tr>
         *  <tr><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
         *  <tr class="fan"><td>一条</td><td>二条</td><td>三条</td><td>四条</td><td>五条</td><td>六条</td><td>七条</td><td>八条</td><td>九条</td></tr>
         *  <tr><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
         *  <tr class="fan"><td>一饼</td><td>二饼</td><td>三饼</td><td>四饼</td><td>五饼</td><td>六饼</td><td>七饼</td><td>八饼</td><td>九饼</td></tr>
         *  <tr><td>31</td><td>32</td><td>33</td><td>34</td><td>35</td><td>36</td><td>37</td><td>38</td><td>39</td></tr>
         *  <tr class="fan"><td>东</td><td>南</td><td>西</td><td>北</td></tr>
         *  <tr><td>41</td><td>42</td><td>43</td><td>44</td></tr>
         *  <tr class="fan"><td>中</td><td>发</td><td>白</td></tr>
         *  <tr><td>51</td><td>52</td><td>53</td></tr>
         *  <tr class="fan"><td>春</td><td>夏</td><td>秋</td><td>冬</td><td>梅</td><td>兰</td><td>竹</td><td>菊</td></tr>
         *  <tr><td>61</td><td>62</td><td>63</td><td>64</td><td>65</td><td>66</td><td>67</td><td>68</td></tr>
         *  </table>
         *  <hr/>
         * </pre>
         * 
         * @return 牌数据
         */
        /*
         * 此数组主要是为了方便某些牌型的判断，特别是一色类的(没有此辅助拷贝的话也能判断，但是会复杂一些)
         * 因为特殊牌型会在每次判断是否和牌时都解析一次，所以在特殊牌型的match方法内，尽量不要调用此方法；而一般牌型只有最后确定和牌时才会解析
         * fullDataOk是内部使用的标志，为true表示数据是有效的，否则是以往数据，不能使用
         */
        public int[] getFullData() {
            if (data[1] == 0)
                return data;
            if (fullData == null)
                fullData = new int[data.length];
            if (!fullDataOk) {
                fullDataOk = true;
                System.arraycopy(data, 0, fullData, 0, data.length);
                for (int i = 2; i <= 8; i += 2) {
                    int p = fullData[i + 1];
                    switch (fullData[i]) {
                        case 0 :
                            return fullData;
                        case CHI_LEFT :
                            fullData[p]++;
                            fullData[p + 1]++;
                            fullData[p + 2]++;
                            break;
                        case CHI_CENTER :
                            fullData[p - 1]++;
                            fullData[p]++;
                            fullData[p + 1]++;
                            break;
                        case CHI_RIGHT :
                            fullData[p - 2]++;
                            fullData[p - 1]++;
                            fullData[p]++;
                            break;
                        case PENG_XIAJIA :
                        case PENG_DUIJIA :
                        case PENG_SHANGJIA :
                        case GANG_XIAJIA :
                        case GANG_DUIJIA :
                        case GANG_SHANGJIA :
                        case GANG_AN :
                            fullData[p] += 3;
                            break;
                    }
                    fullData[p - p % 10] += 3;
                }
            }
            return fullData;
        }

        /**
         * @return 是否是一局的最后一张牌
         */
        public boolean isLast() {
            return this.isLast;
        }

        /**
         * @return 是否是绝张
         */
        public boolean isJuezhang() {
            return this.isJuezhang;
        }
        
        /**
         * @return 是否是抢杠的牌
         */
        public boolean isQianggang() {
            return this.isQianggang;
        }

        /**
         * @return 是否是杠牌后补到的牌
         */
        public boolean isGangbu() {
            return this.isGangbu;
        }

        /**
         * @return 是否是补花后补到的牌
         */
        public boolean isHuabu() {
            return this.isHuabu;
        }

        /**
         * @return 圈风牌号
         */
        /*
         * Pai.this.quanfeng
         */
        public int getQuanfeng() {
            return quanfeng;
        }

        /**
         * @return 门风牌号
         */
        /*
         * Pai.this.menfeng
         */
        public int getMenfeng() {
            return menfeng;
        }

        /**
         * 得到和牌后的牌型及其对应出现的次数
         * <br/>
         * 长度为0表示还没有和牌
         * 
         * @return 和牌后的牌型，还没有和牌时返回长度为0的数组
         */
        public HuPaixing[] getHuPaixings() {
            return huPaixings;
        }
        
        /*
         * 根据who确定type
         * paihao赋值
         * fullData置标志为未填充
         * isLast、isJuezhang、isQianggang、isGangbu、isHuabu置为false
         * huPaixings置空
         */
        private void reset(RelativeFangwei who,
                           int paihao,
                           boolean isLast,
                           boolean isJuezhang,
                           boolean isQianggang,
                           boolean isGangbu,
                           boolean isHuabu) {
            switch (who) {
                case XIAJIA :
                    type = HU_XIAJIA;
                    break;
                case DUIJIA :
                    type = HU_DUIJIA;
                    break;
                case SHANGJIA :
                    type = HU_SHANGJIA;
                    break;
                case ZIJI :
                    type = HU_ZIJI;
                    break;
            }
            this.paihao = paihao;
            if (fullData != null)
                fullDataOk = false;
            this.isLast = isLast;
            this.isJuezhang = isJuezhang;
            this.isQianggang = isQianggang;
            this.isGangbu = isGangbu;
            this.isHuabu = isHuabu;
            huPaixings = new HuPaixing[0];
        }
    }
    
}
